/**
 * @class 恒星系统实体
 * @author DuskyHuang 圣子
 * @description 中心恒星和行星系统
*/

import { _decorator, Node, Vec3, Material, renderer, Color, MeshRenderer } from 'cc';
import { Controller, core, utils } from 'cfw';
import { LAYER_MAIN_3D, Res, SceneGraph } from 'const';
import { cosmos, CosmosProxy, solar, SolarProxy, visit } from 'proxies';
import { createNodeAt } from 'helpers';
import { Comet } from '../solar/comet';
import { View, UI } from 'notifications';
import { Planet } from '../solar/planet';
import { Sun } from '../solar/sun';
import { Dsm } from '../solar/dsm';
import { Belts } from '../solar/belts';
import { attenuate } from 'algorithms';
const { findNode } = utils;
const { ccclass, property } = _decorator;

@ccclass('cocosmos.SolarSystem')
export class SolarSystem extends Controller {

    @property([Material]) public sharedInstMaterials: Material[] = [];
    @property(MeshRenderer) public dust: MeshRenderer = null;

    public get sun(): Sun { return this._sun; }
    public get planets(): Planet[] { return this._planets; }
    public get earth(): Planet { return this._planets.find(p => p.node.name === 'Earth'); }
    public get dwarfs(): Dsm[] { return this._dwarfs; }
    public get minors(): Dsm[] { return this._minors; }
    public get comets(): Comet[] { return this._comets; }
    public getCelestial(name: celestial, type?: Extract<orbit, 'planet' | 'dwarf' | 'minor' | 'comet' | 'belts'>): Planet | Dsm | Comet | Sun | Belts {
        if (name === 'Sun') return this._sun;
        switch (type) {
            case 'planet': return this._planets.find(p => p.node.name === name);
            case 'dwarf': return this._dwarfs.find(p => p.node.name === name);
            case 'minor': return this._minors.find(p => p.node.name === name);
            case 'comet': return this._comets.find(p => p.node.name === name);
            case 'belts': return this._belts;
        }
    }

    protected onLoad(): void { this._init()._listen(); }

    protected update(): void {
        this._sycSolarPos();
        this._updateVisible();
    }

    private _updateVisible(): void {
        const { showSun, showPlanets, showDwarfs, showMinors, showComets, showBelts } = solar;
        this._sun.visible = showSun;
        if (this._containers.planet) this._containers.planet.active = showPlanets;
        if (this._containers.dwarf) this._containers.dwarf.active = showDwarfs;
        this._checkBuilds();
        if (this._containers.minor) this._containers.minor.active = showMinors;
        if (this._containers.comet) this._containers.comet.active = showComets;
        if (this._containers.belts) {
            this._containers.belts.active = showBelts;
            this.dust.node.active = showBelts;
        }
    }

    protected lateUpdate(): void {
        color.set(SolarProxy.BeltColor);
        color.a = 127 * attenuate(cosmos.pow10, BeltAtt, false);
        this._pass0.setUniform(this._handleDust, color);
    }

    private _checkBuilds(): void {
        const { showMinors, showComets, showBelts } = solar;
        if (showMinors && !this._containers.minor) this._buildMinors();
        if (showComets && !this._containers.comet) this._buildComets();
        if (showBelts && !this._containers.belts) this._buildBelts();
    }

    private _pass0: renderer.Pass;
    private _handleDust: handle;
    private _init(): this {
        this._pass0 = this.dust!.sharedMaterial.passes[0];
        this._handleDust = this._pass0.getHandle('beltColor');
        this.dust.node.active = false;
        this._buildSun();
        this._buildLight();
        this._buildPlanets();
        this._buildDwarfs();
        // this._buildMinors();
        // this._buildComets();
        // this._buildBelts();
        return this;
    }

    private _buildSun(): void {
        const name: sun = 'Sun';
        const Template = core.res.getPrefab(Prefabs.SUN);
        const config = core.res.getConfigItem<celestialConfig>(SOLAR_PLANETS, name);
        const viewer = utils.findCamera(COSMOS, MAIN);
        this._sun = createNodeAt(name, LAYER_MAIN_3D, this.node, Template).getComponent(Sun);
        this._sun.loadConfig(config, viewer, BASE_POW10);
        visit.visit(this._sun);
        cosmos.zoomToDur(this._sun.panorama, 1, null, View.Transform.VISIT_OVER);
    }

    private _buildLight(): void {
        this._light = findNode(MAIN_LIGHT)!;
        this._light.parent = this._sun.node;
        this._light.setPosition(Vec3.ZERO);
        this.sharedInstMaterials.forEach(mat => {
            const pass0 = mat.passes[0];
            this._sharedPasses.push(pass0);
            this._sharedHandles.push(pass0.getHandle('solarPos'));
        });
    }

    private _buildPlanets(): void {
        this._containers.planet = createNodeAt('Planets', LAYER_MAIN_3D, this.node);
        const Template = core.res.getPrefab(Prefabs.PLANET);
        const configs = core.res.getConfig<planetConfig>(SOLAR_PLANETS);
        const viewer = utils.findCamera(COSMOS, MAIN);
        let farthest = 0, names = [];
        for (const key in configs) {
            if (key === 'Sun') continue;
            const config = configs[key];
            names.push(key);
            const planet = createNodeAt(key, LAYER_MAIN_3D, this._containers.planet, Template).getComponent(Planet);
            planet.loadConfig(config, viewer, BASE_POW10, this._sun);
            this._planets.push(planet);
            const scope = planet.orbitalRadius;
            if (scope > farthest) farthest = scope;
        }
        solar.reloadParams(this._sun, names, Math.log10(farthest * 2) + BASE_POW10);
    }

    private _buildDwarfs(): void {
        this._containers.dwarf = createNodeAt('Dwarfs', LAYER_MAIN_3D, this.node);
        const Template = core.res.getPrefab(Prefabs.DSM);
        const configs = core.res.getConfig<planetConfig>(SOLAR_DWARFS);
        const viewer = utils.findCamera(COSMOS, MAIN);
        for (const key in configs) {
            const config = configs[key];
            const dwarf = createNodeAt(key, LAYER_MAIN_3D, this._containers.dwarf, Template).getComponent(Dsm);
            dwarf.setType('dwarf');
            dwarf.loadConfig(config, viewer, BASE_POW10, this._sun);
            this._dwarfs.push(dwarf);
        }
    }

    private _buildMinors(): void {
        this._containers.minor = createNodeAt('Minors', LAYER_MAIN_3D, this.node);
        const Template = core.res.getPrefab(Prefabs.DSM);
        const configs = core.res.getConfig<cometConfig>(SOLAR_MINORS);
        const viewer = utils.findCamera(COSMOS, MAIN);
        for (const key in configs) {
            const config = configs[key];
            const minor = createNodeAt(key, LAYER_MAIN_3D, this._containers.minor, Template).getComponent(Dsm);
            minor.setType('minor');
            minor.loadConfig(config, viewer, BASE_POW10, this._sun);
            this._minors.push(minor);
        }
    }

    private _buildComets(): void {
        this._containers.comet = createNodeAt('Comets', LAYER_MAIN_3D, this.node);
        const Template = core.res.getPrefab(Prefabs.COMET);
        const configs = core.res.getConfig<cometConfig>(SOLAR_COMETS);
        const viewer = utils.findCamera(COSMOS, MAIN);
        for (const key in configs) {
            const config = configs[key];
            const comet = createNodeAt(key, LAYER_MAIN_3D, this._containers.comet, Template).getComponent(Comet);
            comet.loadConfig(config, viewer, BASE_POW10, this._sun);
            this._comets.push(comet);
        }
    }

    private _buildBelts(): void {
        this._containers.belts = this.node.getChildByName('Belts')!;
        this._belts = this._containers.belts.getComponent(Belts);
        this._belts.loadAsteroids(this._sun);
    }

    private _sycSolarPos(): void {
        this._sharedPasses.forEach((pass, idx) => pass.setUniform(this._sharedHandles[idx], this._sun.node.worldPosition));
    }
    private _listen(): this {
        this.on(UI.VISIBLE, this._updateVisible, -99);
        return this;
    }

    private _sun: Sun = null;
    private _light: Node = null;
    private _planets: Planet[] = [];
    private _dwarfs: Dsm[] = [];
    private _minors: Dsm[] = [];
    private _comets: Comet[] = [];
    private _belts: Belts = null;
    private _sharedPasses: renderer.Pass[] = [];
    private _sharedHandles: handle[] = [];
    private _containers: Partial<Record<orbit | 'belts', Node>> = {};
}

const { Prefabs, Configs: { SOLAR_PLANETS, SOLAR_DWARFS, SOLAR_MINORS, SOLAR_COMETS, ASTEROID_BELTS } } = Res;
const { COSMOS, Cameras: { MAIN }, MAIN_LIGHT } = SceneGraph;
const BASE_POW10 = CosmosProxy.Scopes.Solar[2];
const color = new Color;
const BeltAtt: attenuation = [-6.5, -5.5];